package com.hibegin.http.server.config;

import com.hibegin.http.server.util.MimeTypeUtil;
import com.hibegin.http.server.web.Controller;
import com.hibegin.http.server.web.Interceptor;
import com.hibegin.http.server.web.Interceptor4AddCrossDomainHeaders;
import com.hibegin.http.server.web.Interceptor4Controller;
import com.hibegin.http.server.web.Interceptor4Default;
import com.hibegin.http.server.web.Interceptor4HttpOptionsMethod;
import com.hibegin.http.server.web.Interceptor4StaticResource;
import com.szwistar.common.datastruct.JsonMapper;
import com.szwistar.common.datastruct.PropertiesEx;
import com.szwistar.common.os.PathUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 服务器配置
 * <p>默认按以下顺序处理 URL 请求：
 * <li>统一处理 OPTIONS 方法请求
 * <li>如果匹配注册的 Controller 方法 -> URL 映射，则创建 Controller 并调用相应的方法
 * <li>在用户自定义的静态资源映射路径(通过 {@link ServerConfig#addStaticResourceMapper()} 添加的路径)中查找文件；如果是目录，则加载默认主页文件；
 * <li>如果文件不存在，则到指定的 jar 包中找；
 * <li>尝试在静态 Web 资源路径(通过 {@link ServerConfig#setWebRootPath()} 配置的)中查找文件；如果是目录，则加载默认主页文件；
 */
public class ServerConfig {
    /**
     * 上传文件命名方法
     */
    public static final int UPLOAD_FILE_NAMING_KEEP 	= 1;	// 保留原文件名
    public static final int UPLOAD_FILE_NAMING_RANDOM 	= 2;	// 随机文件名(文件扩展名保持与原始文件相同)
    public static final int UPLOAD_FILE_NAMING_MD5 		= 3;	// MD5文件名(可防止相同文件重复上传)

    /**
     * 静态资源加载器接口
     */
	public static interface StaticResourceLoader {
	    InputStream getInputStream(String path) throws Exception;
	}

    // 配置参数表
    PropertiesEx config;

	// 监听地址
    private String host = "0.0.0.0";
	// 监听端口号
    private int port = 9404;
	// 是否是 HTTPS
    private boolean isSsl = false;
	// HTTPS 的密钥文件
    private String sslKeystore = null;
	// HTTPS 的密钥文件密码
    private String sslKeystorePassword = null;
	// 超时时间(秒)
    private int timeout = 60;
    // 编码
    private String charset = "UTF-8";
    // 是否保持连接
    private boolean keepAlive = false;
	// 是否支持 GZIP 压缩传输
    private boolean supportGzip = true;
	// 是否支持 HTTP2
    private boolean supportHttp2 = false;
	// HTTP 请求 body 大小限制
    private int maxRequestBodySize = 20971520;
	// 是否禁用 cookie
    private boolean disableCookie = false;
	// HTTP 头中用于保存 Session ID 的字段
    private String sessionId = "JSESSIONID";
	// 默认主页文件
    private String welcomeFile = "index.html";

    // 运行时的工作目录/根目录
    private String rootPath;
    // 临时目录(默认为工作目录中的 temp 目录)
    private String tempPath;
    // 默认 Web 资源根目录
    private String webRootPath = "";
    // 上传文件路径
    private String uploadPath = "";
    // 上传文件命名方法
    private int uploadFileNaming = UPLOAD_FILE_NAMING_KEEP;

    // HTTP 请求 拦截器表
    private final List<Class<? extends Interceptor>> interceptors = new ArrayList<Class<? extends Interceptor>>();
    // 静态资源映射表
    private final Map<String, StaticResourceLoader> staticResourceMapper = new ConcurrentHashMap<String, StaticResourceLoader>();
    // URL -> Controller 映射表
    private Map<String, Controller> controllerMapper = new HashMap<String, Controller>();
    // 用于 JSON 编解码的转换器
    private HttpJsonMessageConverter httpJsonMessageConverter;

    private int decodeThreadCoreCount;
    private int requestThreadMaxCount;
    private int decodeThreadMaxCount;

    public int getDecodeThreadCoreCount() { return decodeThreadCoreCount; }
    public int getRequestThreadMaxCount() { return requestThreadMaxCount; }
	public int getDecodeThreadMaxCount() { return decodeThreadMaxCount; }
	public List<Class<? extends Interceptor>> getInterceptors() { return interceptors; }

    public String getHost() { return host; }
    public void setHost(String host) { this.host = host; }

	public boolean isSsl() { return isSsl; }
    public void setSsl(boolean isSsl) { this.isSsl = isSsl; }

	public String getSslKeystore() { return sslKeystore; }
	public String getSslKeystorePassword() { return sslKeystorePassword; }

	public int getPort() { return port; }
    public void setPort(int port) { this.port = port; }

    public int getTimeout() { return timeout; }
    public void setTimeout(int timeout) { this.timeout = timeout; }

    public String getCharset() { return charset; }
	public void setCharset(String charset) { this.charset = charset; }

	public boolean isKeepAlive() { return keepAlive; }
	public void setKeepAlive(boolean keepAlive) { this.keepAlive = keepAlive; }

	public boolean isSupportGzip() { return supportGzip; }
	public void setSupportGzip(boolean supportGzip) { this.supportGzip = supportGzip; }

	public boolean isSupportHttp2() { return supportHttp2; }
    public void setSupportHttp2(boolean supportHttp2) { this.supportHttp2 = supportHttp2; }

    public String getSessionId() { return sessionId; }
    public void setSessionId(String sessionId) { this.sessionId = sessionId; }

    public boolean isDisableCookie() { return disableCookie; }
    public void setDisableCookie(boolean disableCookie) { this.disableCookie = disableCookie; }

    public Integer getMaxRequestBodySize() { return maxRequestBodySize; }

    public String getRootPath() { return rootPath; }
    public void setRootPath(String rootPath) { this.rootPath = rootPath; }

    public String getTempPath() { return tempPath; }
	public void setTempPath(String tempPath) { this.tempPath = tempPath; }

	public String getWebRootPath() { return webRootPath; }
	public void setWebRootPath(String webRootPath) { this.webRootPath = webRootPath; }
	public Map<String, StaticResourceLoader> getStaticResourceMapper() { return staticResourceMapper; }

    public String getWelcomeFile() { return welcomeFile; }
    public void setWelcomeFile(String welcomeFile) { this.welcomeFile = welcomeFile; }

	public String getUploadPath() { return uploadPath; }
	public void setUploadPath(String uploadPath) { this.uploadPath = uploadPath; }
	public int getUploadFileNaming() { return uploadFileNaming; }
	public void setUploadFileNaming(int uploadFileNaming) { this.uploadFileNaming = uploadFileNaming; }

    public HttpJsonMessageConverter getHttpJsonMessageConverter() { return httpJsonMessageConverter; }
    public void setHttpJsonMessageConverter(HttpJsonMessageConverter httpJsonMessageConverter) { this.httpJsonMessageConverter = httpJsonMessageConverter; }

    public ServerConfig(File rootPath, PropertiesEx config, File mineTypeConfigFile) {
    	this.config = config;

		// 加载 MineType 配置文件
        MimeTypeUtil.loadMimeType(mineTypeConfigFile);

    	if(rootPath != null) {
    		// 使用指定的工作目录
    		if(!rootPath.exists() || !rootPath.isDirectory()) {
    			throw new RuntimeException("指定的工作目录不存在！" + rootPath.toString());
    		}
    		this.rootPath = rootPath.getAbsolutePath();
        } else {
        	// 使用当前目录做为工作目录
        	this.rootPath = PathUtils.getCurrentDir();
        }

    	// 生成 temp 目录
        tempPath = getRootPath() + "/temp/";
        // 确保目录存在
        new File(tempPath).mkdirs();

    	// 如果指定了配置文件，则加载指定的文件
    	if(config != null) {
        	host               = config.get("webServer.listenAddr", host);
        	port               = config.getInt("webServer.listenPort", port);
        	isSsl              = config.getBool("webServer.isHttps", isSsl);
        	sslKeystore        = config.get("webServer.sslKeystore", sslKeystore);
        	sslKeystorePassword= config.get("webServer.sslKeystorePassword", sslKeystorePassword);
        	timeout            = config.getInt("webServer.timeout", timeout);
        	charset            = config.get("webServer.charset", charset);
        	keepAlive          = config.getBool("webServer.keepAlive", keepAlive);
        	supportHttp2       = config.getBool("webServer.supportHttp2", supportHttp2);
        	supportGzip        = config.getBool("webServer.supportGzip", supportGzip);
        	maxRequestBodySize = config.getInt("webServer.maxRequestBodySize", maxRequestBodySize);
        	disableCookie      = config.getBool("webServer.disableCookie", disableCookie);
        	sessionId          = config.get("webServer.sessionId", sessionId);
        	welcomeFile        = config.get("webServer.welcomeFile", welcomeFile);
        	decodeThreadCoreCount = config.getInt("webServer.decodeThreadCoreCount", Runtime.getRuntime().availableProcessors() + 1);
        	requestThreadMaxCount = config.getInt("webServer.requestThreadMaxCount", 20);
        	decodeThreadMaxCount  = config.getInt("webServer.decodeThreadMaxCount", 20);
    	}

        // 添加默认的拦截器
    	interceptors.add(Interceptor4HttpOptionsMethod.class);
    	interceptors.add(Interceptor4AddCrossDomainHeaders.class);
    	interceptors.add(Interceptor4Controller.class);
    	interceptors.add(Interceptor4StaticResource.class);
    	interceptors.add(Interceptor4Default.class);

    	// 添加默认的 JSON 编解器
    	if(httpJsonMessageConverter == null) {
    		httpJsonMessageConverter = new HttpJsonMessageConverter() {
				@Override
				public String toJson(Object obj) throws Exception { return JsonMapper.toJson(obj); }
				@Override
				public Object fromJson(String jsonStr) throws Exception { return JsonMapper.fromJson(jsonStr, Object.class); }
	        };
    	}
    }

    /**
     * 添加拦截器
     */
    public void addInterceptor(Class<? extends Interceptor> interceptor) {
        synchronized (interceptors) {
        	// 如果已经添加过，则不再重复添加
            if (interceptors.contains(interceptor)) { return; }
        	interceptors.add(interceptor);
        }
    }

    /**
     * 映射目录资源到 URI
     * @param uri 要映射到的 URI
     * @param dirPath 静态文件目录
     */
    public void addStaticResourceMapper(String uri, String dirPath) {
    	if(!uri.endsWith("/")) { uri = uri + "/"; }
    	final String rootPath = dirPath.endsWith("/") ? dirPath : dirPath + "/";
        staticResourceMapper.put(uri, new StaticResourceLoader() {
	        @Override
	        public InputStream getInputStream(String path) throws Exception {
	            return new FileInputStream(new File(rootPath, path));
	        }
	    });
    }

    /**
     * 映射类资源到 URI
     * @param uri 要映射到的 URI
     * @param clazz 资源在与此 clazz 相同的目录中
     */
    public void addStaticResourceMapper(String uri, final Class<?> clazz) {
    	if(!uri.endsWith("/")) { uri = uri + "/"; }
        staticResourceMapper.put(uri, new StaticResourceLoader() {
	        @Override
	        public InputStream getInputStream(String path) throws Exception {
	            return clazz.getResourceAsStream(path);
	        }
	    });
    }

    /**
     * 添加 Controller 中的方法映射
     * @param uri 要映射到的 URL 路径
     * @param clazz Controller 实现类。此类中的所有参数为 (HttpRequest, HttpResponse) 的方法都将被映射。如：<pre>
     * class DemoApi implements Controller {
     *     public void sayHello(HttpRequest request, HttpResponse response) {} // 将被映射到 "/demo/sayHello"
     * }
     *
     * addControllerMapper("demo", DemoApi.class);
	 * </pre>
     */
    public void addControllerMapper(String uri, Controller controller) {
    	if(!uri.endsWith("/")) { uri = uri + "/"; }
    	controllerMapper.put(uri, controller);
    }

    public Controller getControllerMapper(String uri) {
    	return controllerMapper.get(uri);
    }
}
